1   /*
2    * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.java2d.pisces;
27  
28  import java.util.Arrays;
29  import java.util.Iterator;
30  import static java.lang.Math.ulp;
31  import static java.lang.Math.sqrt;
32  
33  import sun.awt.geom.PathConsumer2D;
34  
35  // TODO: some of the arithmetic here is too verbose and prone to hard to
36  // debug typos. We should consider making a small Point/Vector class that
37  // has methods like plus(Point), minus(Point), dot(Point), cross(Point)and such
38  final class Stroker implements PathConsumer2D {
39  
40      private static final int MOVE_TO = 0;
41      private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
42      private static final int CLOSE = 2;
43  
44      /**
45       * Constant value for join style.
46       */
47      public static final int JOIN_MITER = 0;
48  
49      /**
50       * Constant value for join style.
51       */
52      public static final int JOIN_ROUND = 1;
53  
54      /**
55       * Constant value for join style.
56       */
57      public static final int JOIN_BEVEL = 2;
58  
59      /**
60       * Constant value for end cap style.
61       */
62      public static final int CAP_BUTT = 0;
63  
64      /**
65       * Constant value for end cap style.
66       */
67      public static final int CAP_ROUND = 1;
68  
69      /**
70       * Constant value for end cap style.
71       */
72      public static final int CAP_SQUARE = 2;
73  
74      private final PathConsumer2D out;
75  
76      private final int capStyle;
77      private final int joinStyle;
78  
79      private final float lineWidth2;
80  
81      private final float[][] offset = new float[3][2];
82      private final float[] miter = new float[2];
83      private final float miterLimitSq;
84  
85      private int prev;
86  
87      // The starting point of the path, and the slope there.
88      private float sx0, sy0, sdx, sdy;
89      // the current point and the slope there.
90      private float cx0, cy0, cdx, cdy; // c stands for current
91      // vectors that when added to (sx0,sy0) and (cx0,cy0) respectively yield the
92      // first and last points on the left parallel path. Since this path is
93      // parallel, it's slope at any point is parallel to the slope of the
94      // original path (thought they may have different directions), so these
95      // could be computed from sdx,sdy and cdx,cdy (and vice versa), but that
96      // would be error prone and hard to read, so we keep these anyway.
97      private float smx, smy, cmx, cmy;
98  
99      private final PolyStack reverse = new PolyStack();
100 
101     /**
102      * Constructs a <code>Stroker</code>.
103      *
104      * @param pc2d an output <code>PathConsumer2D</code>.
105      * @param lineWidth the desired line width in pixels
106      * @param capStyle the desired end cap style, one of
107      * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
108      * <code>CAP_SQUARE</code>.
109      * @param joinStyle the desired line join style, one of
110      * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
111      * <code>JOIN_BEVEL</code>.
112      * @param miterLimit the desired miter limit
113      */
114     public Stroker(PathConsumer2D pc2d,
115                    float lineWidth,
116                    int capStyle,
117                    int joinStyle,
118                    float miterLimit)
119     {
120         this.out = pc2d;
121 
122         this.lineWidth2 = lineWidth / 2;
123         this.capStyle = capStyle;
124         this.joinStyle = joinStyle;
125 
126         float limit = miterLimit * lineWidth2;
127         this.miterLimitSq = limit*limit;
128 
129         this.prev = CLOSE;
130     }
131 
132     private static void computeOffset(final float lx, final float ly,
133                                       final float w, final float[] m)
134     {
135         final float len = (float) sqrt(lx*lx + ly*ly);
136         if (len == 0) {
137             m[0] = m[1] = 0;
138         } else {
139             m[0] = (ly * w)/len;
140             m[1] = -(lx * w)/len;
141         }
142     }
143 
144     // Returns true if the vectors (dx1, dy1) and (dx2, dy2) are
145     // clockwise (if dx1,dy1 needs to be rotated clockwise to close
146     // the smallest angle between it and dx2,dy2).
147     // This is equivalent to detecting whether a point q is on the right side
148     // of a line passing through points p1, p2 where p2 = p1+(dx1,dy1) and
149     // q = p2+(dx2,dy2), which is the same as saying p1, p2, q are in a
150     // clockwise order.
151     // NOTE: "clockwise" here assumes coordinates with 0,0 at the bottom left.
152     private static boolean isCW(final float dx1, final float dy1,
153                                 final float dx2, final float dy2)
154     {
155         return dx1 * dy2 <= dy1 * dx2;
156     }
157 
158     // pisces used to use fixed point arithmetic with 16 decimal digits. I
159     // didn't want to change the values of the constant below when I converted
160     // it to floating point, so that's why the divisions by 2^16 are there.
161     private static final float ROUND_JOIN_THRESHOLD = 1000/65536f;
162 
163     private void drawRoundJoin(float x, float y,
164                                float omx, float omy, float mx, float my,
165                                boolean rev,
166                                float threshold)
167     {
168         if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) {
169             return;
170         }
171 
172         float domx = omx - mx;
173         float domy = omy - my;
174         float len = domx*domx + domy*domy;
175         if (len < threshold) {
176             return;
177         }
178 
179         if (rev) {
180             omx = -omx;
181             omy = -omy;
182             mx = -mx;
183             my = -my;
184         }
185         drawRoundJoin(x, y, omx, omy, mx, my, rev);
186     }
187 
188     private void drawRoundJoin(float cx, float cy,
189                                float omx, float omy,
190                                float mx, float my,
191                                boolean rev)
192     {
193         // The sign of the dot product of mx,my and omx,omy is equal to the
194         // the sign of the cosine of ext
195         // (ext is the angle between omx,omy and mx,my).
196         double cosext = omx * mx + omy * my;
197         // If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
198         // need 1 curve to approximate the circle section that joins omx,omy
199         // and mx,my.
200         final int numCurves = cosext >= 0 ? 1 : 2;
201 
202         switch (numCurves) {
203         case 1:
204             drawBezApproxForArc(cx, cy, omx, omy, mx, my, rev);
205             break;
206         case 2:
207             // we need to split the arc into 2 arcs spanning the same angle.
208             // The point we want will be one of the 2 intersections of the
209             // perpendicular bisector of the chord (omx,omy)->(mx,my) and the
210             // circle. We could find this by scaling the vector
211             // (omx+mx, omy+my)/2 so that it has length=lineWidth2 (and thus lies
212             // on the circle), but that can have numerical problems when the angle
213             // between omx,omy and mx,my is close to 180 degrees. So we compute a
214             // normal of (omx,omy)-(mx,my). This will be the direction of the
215             // perpendicular bisector. To get one of the intersections, we just scale
216             // this vector that its length is lineWidth2 (this works because the
217             // perpendicular bisector goes through the origin). This scaling doesn't
218             // have numerical problems because we know that lineWidth2 divided by
219             // this normal's length is at least 0.5 and at most sqrt(2)/2 (because
220             // we know the angle of the arc is > 90 degrees).
221             float nx = my - omy, ny = omx - mx;
222             float nlen = (float) sqrt(nx*nx + ny*ny);
223             float scale = lineWidth2/nlen;
224             float mmx = nx * scale, mmy = ny * scale;
225 
226             // if (isCW(omx, omy, mx, my) != isCW(mmx, mmy, mx, my)) then we've
227             // computed the wrong intersection so we get the other one.
228             // The test above is equivalent to if (rev).
229             if (rev) {
230                 mmx = -mmx;
231                 mmy = -mmy;
232             }
233             drawBezApproxForArc(cx, cy, omx, omy, mmx, mmy, rev);
234             drawBezApproxForArc(cx, cy, mmx, mmy, mx, my, rev);
235             break;
236         }
237     }
238 
239     // the input arc defined by omx,omy and mx,my must span <= 90 degrees.
240     private void drawBezApproxForArc(final float cx, final float cy,
241                                      final float omx, final float omy,
242                                      final float mx, final float my,
243                                      boolean rev)
244     {
245         float cosext2 = (omx * mx + omy * my) / (2 * lineWidth2 * lineWidth2);
246         // cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
247         // (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that
248         // define the bezier curve we're computing.
249         // It is computed using the constraints that P1-P0 and P3-P2 are parallel
250         // to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|.
251         float cv = (float) ((4.0 / 3.0) * sqrt(0.5-cosext2) /
252                             (1.0 + sqrt(cosext2+0.5)));
253         // if clockwise, we need to negate cv.
254         if (rev) { // rev is equivalent to isCW(omx, omy, mx, my)
255             cv = -cv;
256         }
257         final float x1 = cx + omx;
258         final float y1 = cy + omy;
259         final float x2 = x1 - cv * omy;
260         final float y2 = y1 + cv * omx;
261 
262         final float x4 = cx + mx;
263         final float y4 = cy + my;
264         final float x3 = x4 + cv * my;
265         final float y3 = y4 - cv * mx;
266 
267         emitCurveTo(x1, y1, x2, y2, x3, y3, x4, y4, rev);
268     }
269 
270     private void drawRoundCap(float cx, float cy, float mx, float my) {
271         final float C = 0.5522847498307933f;
272         // the first and second arguments of the following two calls
273         // are really will be ignored by emitCurveTo (because of the false),
274         // but we put them in anyway, as opposed to just giving it 4 zeroes,
275         // because it's just 4 additions and it's not good to rely on this
276         // sort of assumption (right now it's true, but that may change).
277         emitCurveTo(cx+mx,      cy+my,
278                     cx+mx-C*my, cy+my+C*mx,
279                     cx-my+C*mx, cy+mx+C*my,
280                     cx-my,      cy+mx,
281                     false);
282         emitCurveTo(cx-my,      cy+mx,
283                     cx-my-C*mx, cy+mx-C*my,
284                     cx-mx-C*my, cy-my+C*mx,
285                     cx-mx,      cy-my,
286                     false);
287     }
288 
289     // Put the intersection point of the lines (x0, y0) -> (x1, y1)
290     // and (x0p, y0p) -> (x1p, y1p) in m[off] and m[off+1].
291     // If the lines are parallel, it will put a non finite number in m.
292     private void computeIntersection(final float x0, final float y0,
293                                      final float x1, final float y1,
294                                      final float x0p, final float y0p,
295                                      final float x1p, final float y1p,
296                                      final float[] m, int off)
297     {
298         float x10 = x1 - x0;
299         float y10 = y1 - y0;
300         float x10p = x1p - x0p;
301         float y10p = y1p - y0p;
302 
303         float den = x10*y10p - x10p*y10;
304         float t = x10p*(y0-y0p) - y10p*(x0-x0p);
305         t /= den;
306         m[off++] = x0 + t*x10;
307         m[off] = y0 + t*y10;
308     }
309 
310     private void drawMiter(final float pdx, final float pdy,
311                            final float x0, final float y0,
312                            final float dx, final float dy,
313                            float omx, float omy, float mx, float my,
314                            boolean rev)
315     {
316         if ((mx == omx && my == omy) ||
317             (pdx == 0 && pdy == 0) ||
318             (dx == 0 && dy == 0))
319         {
320             return;
321         }
322 
323         if (rev) {
324             omx = -omx;
325             omy = -omy;
326             mx = -mx;
327             my = -my;
328         }
329 
330         computeIntersection((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
331                             (dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
332                             miter, 0);
333 
334         float lenSq = (miter[0]-x0)*(miter[0]-x0) + (miter[1]-y0)*(miter[1]-y0);
335 
336         // If the lines are parallel, lenSq will be either NaN or +inf
337         // (actually, I'm not sure if the latter is possible. The important
338         // thing is that -inf is not possible, because lenSq is a square).
339         // For both of those values, the comparison below will fail and
340         // no miter will be drawn, which is correct.
341         if (lenSq < miterLimitSq) {
342             emitLineTo(miter[0], miter[1], rev);
343         }
344     }
345 
346     public void moveTo(float x0, float y0) {
347         if (prev == DRAWING_OP_TO) {
348             finish();
349         }
350         this.sx0 = this.cx0 = x0;
351         this.sy0 = this.cy0 = y0;
352         this.cdx = this.sdx = 1;
353         this.cdy = this.sdy = 0;
354         this.prev = MOVE_TO;
355     }
356 
357     public void lineTo(float x1, float y1) {
358         float dx = x1 - cx0;
359         float dy = y1 - cy0;
360         if (dx == 0f && dy == 0f) {
361             dx = 1;
362         }
363         computeOffset(dx, dy, lineWidth2, offset[0]);
364         float mx = offset[0][0];
365         float my = offset[0][1];
366 
367         drawJoin(cdx, cdy, cx0, cy0, dx, dy, cmx, cmy, mx, my);
368 
369         emitLineTo(cx0 + mx, cy0 + my);
370         emitLineTo(x1 + mx, y1 + my);
371 
372         emitLineTo(cx0 - mx, cy0 - my, true);
373         emitLineTo(x1 - mx, y1 - my, true);
374 
375         this.cmx = mx;
376         this.cmy = my;
377         this.cdx = dx;
378         this.cdy = dy;
379         this.cx0 = x1;
380         this.cy0 = y1;
381         this.prev = DRAWING_OP_TO;
382     }
383 
384     public void closePath() {
385         if (prev != DRAWING_OP_TO) {
386             if (prev == CLOSE) {
387                 return;
388             }
389             emitMoveTo(cx0, cy0 - lineWidth2);
390             this.cmx = this.smx = 0;
391             this.cmy = this.smy = -lineWidth2;
392             this.cdx = this.sdx = 1;
393             this.cdy = this.sdy = 0;
394             finish();
395             return;
396         }
397 
398         if (cx0 != sx0 || cy0 != sy0) {
399             lineTo(sx0, sy0);
400         }
401 
402         drawJoin(cdx, cdy, cx0, cy0, sdx, sdy, cmx, cmy, smx, smy);
403 
404         emitLineTo(sx0 + smx, sy0 + smy);
405 
406         emitMoveTo(sx0 - smx, sy0 - smy);
407         emitReverse();
408 
409         this.prev = CLOSE;
410         emitClose();
411     }
412 
413     private void emitReverse() {
414         while(!reverse.isEmpty()) {
415             reverse.pop(out);
416         }
417     }
418 
419     public void pathDone() {
420         if (prev == DRAWING_OP_TO) {
421             finish();
422         }
423 
424         out.pathDone();
425         // this shouldn't matter since this object won't be used
426         // after the call to this method.
427         this.prev = CLOSE;
428     }
429 
430     private void finish() {
431         if (capStyle == CAP_ROUND) {
432             drawRoundCap(cx0, cy0, cmx, cmy);
433         } else if (capStyle == CAP_SQUARE) {
434             emitLineTo(cx0 - cmy + cmx, cy0 + cmx + cmy);
435             emitLineTo(cx0 - cmy - cmx, cy0 + cmx - cmy);
436         }
437 
438         emitReverse();
439 
440         if (capStyle == CAP_ROUND) {
441             drawRoundCap(sx0, sy0, -smx, -smy);
442         } else if (capStyle == CAP_SQUARE) {
443             emitLineTo(sx0 + smy - smx, sy0 - smx - smy);
444             emitLineTo(sx0 + smy + smx, sy0 - smx + smy);
445         }
446 
447         emitClose();
448     }
449 
450     private void emitMoveTo(final float x0, final float y0) {
451         out.moveTo(x0, y0);
452     }
453 
454     private void emitLineTo(final float x1, final float y1) {
455         out.lineTo(x1, y1);
456     }
457 
458     private void emitLineTo(final float x1, final float y1,
459                             final boolean rev)
460     {
461         if (rev) {
462             reverse.pushLine(x1, y1);
463         } else {
464             emitLineTo(x1, y1);
465         }
466     }
467 
468     private void emitQuadTo(final float x0, final float y0,
469                             final float x1, final float y1,
470                             final float x2, final float y2, final boolean rev)
471     {
472         if (rev) {
473             reverse.pushQuad(x0, y0, x1, y1);
474         } else {
475             out.quadTo(x1, y1, x2, y2);
476         }
477     }
478 
479     private void emitCurveTo(final float x0, final float y0,
480                              final float x1, final float y1,
481                              final float x2, final float y2,
482                              final float x3, final float y3, final boolean rev)
483     {
484         if (rev) {
485             reverse.pushCubic(x0, y0, x1, y1, x2, y2);
486         } else {
487             out.curveTo(x1, y1, x2, y2, x3, y3);
488         }
489     }
490 
491     private void emitClose() {
492         out.closePath();
493     }
494 
495     private void drawJoin(float pdx, float pdy,
496                           float x0, float y0,
497                           float dx, float dy,
498                           float omx, float omy,
499                           float mx, float my)
500     {
501         if (prev != DRAWING_OP_TO) {
502             emitMoveTo(x0 + mx, y0 + my);
503             this.sdx = dx;
504             this.sdy = dy;
505             this.smx = mx;
506             this.smy = my;
507         } else {
508             boolean cw = isCW(pdx, pdy, dx, dy);
509             if (joinStyle == JOIN_MITER) {
510                 drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
511             } else if (joinStyle == JOIN_ROUND) {
512                 drawRoundJoin(x0, y0,
513                               omx, omy,
514                               mx, my, cw,
515                               ROUND_JOIN_THRESHOLD);
516             }
517             emitLineTo(x0, y0, !cw);
518         }
519         prev = DRAWING_OP_TO;
520     }
521 
522     private static boolean within(final float x1, final float y1,
523                                   final float x2, final float y2,
524                                   final float ERR)
525     {
526         assert ERR > 0 : "";
527         // compare taxicab distance. ERR will always be small, so using
528         // true distance won't give much benefit
529         return (Helpers.within(x1, x2, ERR) &&  // we want to avoid calling Math.abs
530                 Helpers.within(y1, y2, ERR)); // this is just as good.
531     }
532 
533     private void getLineOffsets(float x1, float y1,
534                                 float x2, float y2,
535                                 float[] left, float[] right) {
536         computeOffset(x2 - x1, y2 - y1, lineWidth2, offset[0]);
537         left[0] = x1 + offset[0][0];
538         left[1] = y1 + offset[0][1];
539         left[2] = x2 + offset[0][0];
540         left[3] = y2 + offset[0][1];
541         right[0] = x1 - offset[0][0];
542         right[1] = y1 - offset[0][1];
543         right[2] = x2 - offset[0][0];
544         right[3] = y2 - offset[0][1];
545     }
546 
547     private int computeOffsetCubic(float[] pts, final int off,
548                                    float[] leftOff, float[] rightOff)
549     {
550         // if p1=p2 or p3=p4 it means that the derivative at the endpoint
551         // vanishes, which creates problems with computeOffset. Usually
552         // this happens when this stroker object is trying to winden
553         // a curve with a cusp. What happens is that curveTo splits
554         // the input curve at the cusp, and passes it to this function.
555         // because of inaccuracies in the splitting, we consider points
556         // equal if they're very close to each other.
557         final float x1 = pts[off + 0], y1 = pts[off + 1];
558         final float x2 = pts[off + 2], y2 = pts[off + 3];
559         final float x3 = pts[off + 4], y3 = pts[off + 5];
560         final float x4 = pts[off + 6], y4 = pts[off + 7];
561 
562         float dx4 = x4 - x3;
563         float dy4 = y4 - y3;
564         float dx1 = x2 - x1;
565         float dy1 = y2 - y1;
566 
567         // if p1 == p2 && p3 == p4: draw line from p1->p4, unless p1 == p4,
568         // in which case ignore if p1 == p2
569         final boolean p1eqp2 = within(x1,y1,x2,y2, 6 * ulp(y2));
570         final boolean p3eqp4 = within(x3,y3,x4,y4, 6 * ulp(y4));
571         if (p1eqp2 && p3eqp4) {
572             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
573             return 4;
574         } else if (p1eqp2) {
575             dx1 = x3 - x1;
576             dy1 = y3 - y1;
577         } else if (p3eqp4) {
578             dx4 = x4 - x2;
579             dy4 = y4 - y2;
580         }
581 
582         // if p2-p1 and p4-p3 are parallel, that must mean this curve is a line
583         float dotsq = (dx1 * dx4 + dy1 * dy4);
584         dotsq = dotsq * dotsq;
585         float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
586         if (Helpers.within(dotsq, l1sq * l4sq, 4 * ulp(dotsq))) {
587             getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
588             return 4;
589         }
590 
591 //      What we're trying to do in this function is to approximate an ideal
592 //      offset curve (call it I) of the input curve B using a bezier curve Bp.
593 //      The constraints I use to get the equations are:
594 //
595 //      1. The computed curve Bp should go through I(0) and I(1). These are
596 //      x1p, y1p, x4p, y4p, which are p1p and p4p. We still need to find
597 //      4 variables: the x and y components of p2p and p3p (i.e. x2p, y2p, x3p, y3p).
598 //
599 //      2. Bp should have slope equal in absolute value to I at the endpoints. So,
600 //      (by the way, the operator || in the comments below means "aligned with".
601 //      It is defined on vectors, so when we say I'(0) || Bp'(0) we mean that
602 //      vectors I'(0) and Bp'(0) are aligned, which is the same as saying
603 //      that the tangent lines of I and Bp at 0 are parallel. Mathematically
604 //      this means (I'(t) || Bp'(t)) <==> (I'(t) = c * Bp'(t)) where c is some
605 //      nonzero constant.)
606 //      I'(0) || Bp'(0) and I'(1) || Bp'(1). Obviously, I'(0) || B'(0) and
607 //      I'(1) || B'(1); therefore, Bp'(0) || B'(0) and Bp'(1) || B'(1).
608 //      We know that Bp'(0) || (p2p-p1p) and Bp'(1) || (p4p-p3p) and the same
609 //      is true for any bezier curve; therefore, we get the equations
610 //          (1) p2p = c1 * (p2-p1) + p1p
611 //          (2) p3p = c2 * (p4-p3) + p4p
612 //      We know p1p, p4p, p2, p1, p3, and p4; therefore, this reduces the number
613 //      of unknowns from 4 to 2 (i.e. just c1 and c2).
614 //      To eliminate these 2 unknowns we use the following constraint:
615 //
616 //      3. Bp(0.5) == I(0.5). Bp(0.5)=(x,y) and I(0.5)=(xi,yi), and I should note
617 //      that I(0.5) is *the only* reason for computing dxm,dym. This gives us
618 //          (3) Bp(0.5) = (p1p + 3 * (p2p + p3p) + p4p)/8, which is equivalent to
619 //          (4) p2p + p3p = (Bp(0.5)*8 - p1p - p4p) / 3
620 //      We can substitute (1) and (2) from above into (4) and we get:
621 //          (5) c1*(p2-p1) + c2*(p4-p3) = (Bp(0.5)*8 - p1p - p4p)/3 - p1p - p4p
622 //      which is equivalent to
623 //          (6) c1*(p2-p1) + c2*(p4-p3) = (4/3) * (Bp(0.5) * 2 - p1p - p4p)
624 //
625 //      The right side of this is a 2D vector, and we know I(0.5), which gives us
626 //      Bp(0.5), which gives us the value of the right side.
627 //      The left side is just a matrix vector multiplication in disguise. It is
628 //
629 //      [x2-x1, x4-x3][c1]
630 //      [y2-y1, y4-y3][c2]
631 //      which, is equal to
632 //      [dx1, dx4][c1]
633 //      [dy1, dy4][c2]
634 //      At this point we are left with a simple linear system and we solve it by
635 //      getting the inverse of the matrix above. Then we use [c1,c2] to compute
636 //      p2p and p3p.
637 
638         float x = 0.125f * (x1 + 3 * (x2 + x3) + x4);
639         float y = 0.125f * (y1 + 3 * (y2 + y3) + y4);
640         // (dxm,dym) is some tangent of B at t=0.5. This means it's equal to
641         // c*B'(0.5) for some constant c.
642         float dxm = x3 + x4 - x1 - x2, dym = y3 + y4 - y1 - y2;
643 
644         // this computes the offsets at t=0, 0.5, 1, using the property that
645         // for any bezier curve the vectors p2-p1 and p4-p3 are parallel to
646         // the (dx/dt, dy/dt) vectors at the endpoints.
647         computeOffset(dx1, dy1, lineWidth2, offset[0]);
648         computeOffset(dxm, dym, lineWidth2, offset[1]);
649         computeOffset(dx4, dy4, lineWidth2, offset[2]);
650         float x1p = x1 + offset[0][0]; // start
651         float y1p = y1 + offset[0][1]; // point
652         float xi  = x + offset[1][0]; // interpolation
653         float yi  = y + offset[1][1]; // point
654         float x4p = x4 + offset[2][0]; // end
655         float y4p = y4 + offset[2][1]; // point
656 
657         float invdet43 = 4f / (3f * (dx1 * dy4 - dy1 * dx4));
658 
659         float two_pi_m_p1_m_p4x = 2*xi - x1p - x4p;
660         float two_pi_m_p1_m_p4y = 2*yi - y1p - y4p;
661         float c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
662         float c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
663 
664         float x2p, y2p, x3p, y3p;
665         x2p = x1p + c1*dx1;
666         y2p = y1p + c1*dy1;
667         x3p = x4p + c2*dx4;
668         y3p = y4p + c2*dy4;
669 
670         leftOff[0] = x1p; leftOff[1] = y1p;
671         leftOff[2] = x2p; leftOff[3] = y2p;
672         leftOff[4] = x3p; leftOff[5] = y3p;
673         leftOff[6] = x4p; leftOff[7] = y4p;
674 
675         x1p = x1 - offset[0][0]; y1p = y1 - offset[0][1];
676         xi = xi - 2 * offset[1][0]; yi = yi - 2 * offset[1][1];
677         x4p = x4 - offset[2][0]; y4p = y4 - offset[2][1];
678 
679         two_pi_m_p1_m_p4x = 2*xi - x1p - x4p;
680         two_pi_m_p1_m_p4y = 2*yi - y1p - y4p;
681         c1 = invdet43 * (dy4 * two_pi_m_p1_m_p4x - dx4 * two_pi_m_p1_m_p4y);
682         c2 = invdet43 * (dx1 * two_pi_m_p1_m_p4y - dy1 * two_pi_m_p1_m_p4x);
683 
684         x2p = x1p + c1*dx1;
685         y2p = y1p + c1*dy1;
686         x3p = x4p + c2*dx4;
687         y3p = y4p + c2*dy4;
688 
689         rightOff[0] = x1p; rightOff[1] = y1p;
690         rightOff[2] = x2p; rightOff[3] = y2p;
691         rightOff[4] = x3p; rightOff[5] = y3p;
692         rightOff[6] = x4p; rightOff[7] = y4p;
693         return 8;
694     }
695 
696     // return the kind of curve in the right and left arrays.
697     private int computeOffsetQuad(float[] pts, final int off,
698                                   float[] leftOff, float[] rightOff)
699     {
700         final float x1 = pts[off + 0], y1 = pts[off + 1];
701         final float x2 = pts[off + 2], y2 = pts[off + 3];
702         final float x3 = pts[off + 4], y3 = pts[off + 5];
703 
704         final float dx3 = x3 - x2;
705         final float dy3 = y3 - y2;
706         final float dx1 = x2 - x1;
707         final float dy1 = y2 - y1;
708 
709         // this computes the offsets at t = 0, 1
710         computeOffset(dx1, dy1, lineWidth2, offset[0]);
711         computeOffset(dx3, dy3, lineWidth2, offset[1]);
712 
713         leftOff[0]  = x1 + offset[0][0];  leftOff[1] = y1 + offset[0][1];
714         leftOff[4]  = x3 + offset[1][0];  leftOff[5] = y3 + offset[1][1];
715         rightOff[0] = x1 - offset[0][0]; rightOff[1] = y1 - offset[0][1];
716         rightOff[4] = x3 - offset[1][0]; rightOff[5] = y3 - offset[1][1];
717 
718         float x1p = leftOff[0]; // start
719         float y1p = leftOff[1]; // point
720         float x3p = leftOff[4]; // end
721         float y3p = leftOff[5]; // point
722 
723         // Corner cases:
724         // 1. If the two control vectors are parallel, we'll end up with NaN's
725         //    in leftOff (and rightOff in the body of the if below), so we'll
726         //    do getLineOffsets, which is right.
727         // 2. If the first or second two points are equal, then (dx1,dy1)==(0,0)
728         //    or (dx3,dy3)==(0,0), so (x1p, y1p)==(x1p+dx1, y1p+dy1)
729         //    or (x3p, y3p)==(x3p-dx3, y3p-dy3), which means that
730         //    computeIntersection will put NaN's in leftOff and right off, and
731         //    we will do getLineOffsets, which is right.
732         computeIntersection(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);
733         float cx = leftOff[2];
734         float cy = leftOff[3];
735 
736         if (!(isFinite(cx) && isFinite(cy))) {
737             // maybe the right path is not degenerate.
738             x1p = rightOff[0];
739             y1p = rightOff[1];
740             x3p = rightOff[4];
741             y3p = rightOff[5];
742             computeIntersection(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);
743             cx = rightOff[2];
744             cy = rightOff[3];
745             if (!(isFinite(cx) && isFinite(cy))) {
746                 // both are degenerate. This curve is a line.
747                 getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
748                 return 4;
749             }
750             // {left,right}Off[0,1,4,5] are already set to the correct values.
751             leftOff[2] = 2*x2 - cx;
752             leftOff[3] = 2*y2 - cy;
753             return 6;
754         }
755 
756         // rightOff[2,3] = (x2,y2) - ((left_x2, left_y2) - (x2, y2))
757         // == 2*(x2, y2) - (left_x2, left_y2)
758         rightOff[2] = 2*x2 - cx;
759         rightOff[3] = 2*y2 - cy;
760         return 6;
761     }
762 
763     private static boolean isFinite(float x) {
764         return (Float.NEGATIVE_INFINITY < x && x < Float.POSITIVE_INFINITY);
765     }
766 
767     // This is where the curve to be processed is put. We give it
768     // enough room to store 2 curves: one for the current subdivision, the
769     // other for the rest of the curve.
770     private float[] middle = new float[2*8];
771     private float[] lp = new float[8];
772     private float[] rp = new float[8];
773     private static final int MAX_N_CURVES = 11;
774     private float[] subdivTs = new float[MAX_N_CURVES - 1];
775 
776     // If this class is compiled with ecj, then Hotspot crashes when OSR
777     // compiling this function. See bugs 7004570 and 6675699
778     // TODO: until those are fixed, we should work around that by
779     // manually inlining this into curveTo and quadTo.
780 /******************************* WORKAROUND **********************************
781     private void somethingTo(final int type) {
782         // need these so we can update the state at the end of this method
783         final float xf = middle[type-2], yf = middle[type-1];
784         float dxs = middle[2] - middle[0];
785         float dys = middle[3] - middle[1];
786         float dxf = middle[type - 2] - middle[type - 4];
787         float dyf = middle[type - 1] - middle[type - 3];
788         switch(type) {
789         case 6:
790             if ((dxs == 0f && dys == 0f) ||
791                 (dxf == 0f && dyf == 0f)) {
792                dxs = dxf = middle[4] - middle[0];
793                dys = dyf = middle[5] - middle[1];
794             }
795             break;
796         case 8:
797             boolean p1eqp2 = (dxs == 0f && dys == 0f);
798             boolean p3eqp4 = (dxf == 0f && dyf == 0f);
799             if (p1eqp2) {
800                 dxs = middle[4] - middle[0];
801                 dys = middle[5] - middle[1];
802                 if (dxs == 0f && dys == 0f) {
803                     dxs = middle[6] - middle[0];
804                     dys = middle[7] - middle[1];
805                 }
806             }
807             if (p3eqp4) {
808                 dxf = middle[6] - middle[2];
809                 dyf = middle[7] - middle[3];
810                 if (dxf == 0f && dyf == 0f) {
811                     dxf = middle[6] - middle[0];
812                     dyf = middle[7] - middle[1];
813                 }
814             }
815         }
816         if (dxs == 0f && dys == 0f) {
817             // this happens iff the "curve" is just a point
818             lineTo(middle[0], middle[1]);
819             return;
820         }
821         // if these vectors are too small, normalize them, to avoid future
822         // precision problems.
823         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
824             float len = (float) sqrt(dxs*dxs + dys*dys);
825             dxs /= len;
826             dys /= len;
827         }
828         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
829             float len = (float) sqrt(dxf*dxf + dyf*dyf);
830             dxf /= len;
831             dyf /= len;
832         }
833 
834         computeOffset(dxs, dys, lineWidth2, offset[0]);
835         final float mx = offset[0][0];
836         final float my = offset[0][1];
837         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my);
838 
839         int nSplits = findSubdivPoints(middle, subdivTs, type, lineWidth2);
840 
841         int kind = 0;
842         Iterator<Integer> it = Curve.breakPtsAtTs(middle, type, subdivTs, nSplits);
843         while(it.hasNext()) {
844             int curCurveOff = it.next();
845 
846             switch (type) {
847             case 8:
848                 kind = computeOffsetCubic(middle, curCurveOff, lp, rp);
849                 break;
850             case 6:
851                 kind = computeOffsetQuad(middle, curCurveOff, lp, rp);
852                 break;
853             }
854             emitLineTo(lp[0], lp[1]);
855             switch(kind) {
856             case 8:
857                 emitCurveTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], lp[6], lp[7], false);
858                 emitCurveTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7], true);
859                 break;
860             case 6:
861                 emitQuadTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], false);
862                 emitQuadTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], true);
863                 break;
864             case 4:
865                 emitLineTo(lp[2], lp[3]);
866                 emitLineTo(rp[0], rp[1], true);
867                 break;
868             }
869             emitLineTo(rp[kind - 2], rp[kind - 1], true);
870         }
871 
872         this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2;
873         this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2;
874         this.cdx = dxf;
875         this.cdy = dyf;
876         this.cx0 = xf;
877         this.cy0 = yf;
878         this.prev = DRAWING_OP_TO;
879     }
880 ****************************** END WORKAROUND *******************************/
881 
882     // finds values of t where the curve in pts should be subdivided in order
883     // to get good offset curves a distance of w away from the middle curve.
884     // Stores the points in ts, and returns how many of them there were.
885     private static Curve c = new Curve();
886     private static int findSubdivPoints(float[] pts, float[] ts, final int type, final float w)
887     {
888         final float x12 = pts[2] - pts[0];
889         final float y12 = pts[3] - pts[1];
890         // if the curve is already parallel to either axis we gain nothing
891         // from rotating it.
892         if (y12 != 0f && x12 != 0f) {
893             // we rotate it so that the first vector in the control polygon is
894             // parallel to the x-axis. This will ensure that rotated quarter
895             // circles won't be subdivided.
896             final float hypot = (float) sqrt(x12 * x12 + y12 * y12);
897             final float cos = x12 / hypot;
898             final float sin = y12 / hypot;
899             final float x1 = cos * pts[0] + sin * pts[1];
900             final float y1 = cos * pts[1] - sin * pts[0];
901             final float x2 = cos * pts[2] + sin * pts[3];
902             final float y2 = cos * pts[3] - sin * pts[2];
903             final float x3 = cos * pts[4] + sin * pts[5];
904             final float y3 = cos * pts[5] - sin * pts[4];
905             switch(type) {
906             case 8:
907                 final float x4 = cos * pts[6] + sin * pts[7];
908                 final float y4 = cos * pts[7] - sin * pts[6];
909                 c.set(x1, y1, x2, y2, x3, y3, x4, y4);
910                 break;
911             case 6:
912                 c.set(x1, y1, x2, y2, x3, y3);
913                 break;
914             }
915         } else {
916             c.set(pts, type);
917         }
918 
919         int ret = 0;
920         // we subdivide at values of t such that the remaining rotated
921         // curves are monotonic in x and y.
922         ret += c.dxRoots(ts, ret);
923         ret += c.dyRoots(ts, ret);
924         // subdivide at inflection points.
925         if (type == 8) {
926             // quadratic curves can't have inflection points
927             ret += c.infPoints(ts, ret);
928         }
929 
930         // now we must subdivide at points where one of the offset curves will have
931         // a cusp. This happens at ts where the radius of curvature is equal to w.
932         ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
933 
934         ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
935         Helpers.isort(ts, 0, ret);
936         return ret;
937     }
938 
939     @Override public void curveTo(float x1, float y1,
940                                   float x2, float y2,
941                                   float x3, float y3)
942     {
943         middle[0] = cx0; middle[1] = cy0;
944         middle[2] = x1;  middle[3] = y1;
945         middle[4] = x2;  middle[5] = y2;
946         middle[6] = x3;  middle[7] = y3;
947 
948         // inlined version of somethingTo(8);
949         // See the TODO on somethingTo
950 
951         // need these so we can update the state at the end of this method
952         final float xf = middle[6], yf = middle[7];
953         float dxs = middle[2] - middle[0];
954         float dys = middle[3] - middle[1];
955         float dxf = middle[6] - middle[4];
956         float dyf = middle[7] - middle[5];
957 
958         boolean p1eqp2 = (dxs == 0f && dys == 0f);
959         boolean p3eqp4 = (dxf == 0f && dyf == 0f);
960         if (p1eqp2) {
961             dxs = middle[4] - middle[0];
962             dys = middle[5] - middle[1];
963             if (dxs == 0f && dys == 0f) {
964                 dxs = middle[6] - middle[0];
965                 dys = middle[7] - middle[1];
966             }
967         }
968         if (p3eqp4) {
969             dxf = middle[6] - middle[2];
970             dyf = middle[7] - middle[3];
971             if (dxf == 0f && dyf == 0f) {
972                 dxf = middle[6] - middle[0];
973                 dyf = middle[7] - middle[1];
974             }
975         }
976         if (dxs == 0f && dys == 0f) {
977             // this happens iff the "curve" is just a point
978             lineTo(middle[0], middle[1]);
979             return;
980         }
981 
982         // if these vectors are too small, normalize them, to avoid future
983         // precision problems.
984         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
985             float len = (float) sqrt(dxs*dxs + dys*dys);
986             dxs /= len;
987             dys /= len;
988         }
989         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
990             float len = (float) sqrt(dxf*dxf + dyf*dyf);
991             dxf /= len;
992             dyf /= len;
993         }
994 
995         computeOffset(dxs, dys, lineWidth2, offset[0]);
996         final float mx = offset[0][0];
997         final float my = offset[0][1];
998         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my);
999 
1000         int nSplits = findSubdivPoints(middle, subdivTs, 8, lineWidth2);
1001 
1002         int kind = 0;
1003         Iterator<Integer> it = Curve.breakPtsAtTs(middle, 8, subdivTs, nSplits);
1004         while(it.hasNext()) {
1005             int curCurveOff = it.next();
1006 
1007             kind = computeOffsetCubic(middle, curCurveOff, lp, rp);
1008             emitLineTo(lp[0], lp[1]);
1009             switch(kind) {
1010             case 8:
1011                 emitCurveTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], lp[6], lp[7], false);
1012                 emitCurveTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7], true);
1013                 break;
1014             case 4:
1015                 emitLineTo(lp[2], lp[3]);
1016                 emitLineTo(rp[0], rp[1], true);
1017                 break;
1018             }
1019             emitLineTo(rp[kind - 2], rp[kind - 1], true);
1020         }
1021 
1022         this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2;
1023         this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2;
1024         this.cdx = dxf;
1025         this.cdy = dyf;
1026         this.cx0 = xf;
1027         this.cy0 = yf;
1028         this.prev = DRAWING_OP_TO;
1029     }
1030 
1031     @Override public void quadTo(float x1, float y1, float x2, float y2) {
1032         middle[0] = cx0; middle[1] = cy0;
1033         middle[2] = x1;  middle[3] = y1;
1034         middle[4] = x2;  middle[5] = y2;
1035 
1036         // inlined version of somethingTo(8);
1037         // See the TODO on somethingTo
1038 
1039         // need these so we can update the state at the end of this method
1040         final float xf = middle[4], yf = middle[5];
1041         float dxs = middle[2] - middle[0];
1042         float dys = middle[3] - middle[1];
1043         float dxf = middle[4] - middle[2];
1044         float dyf = middle[5] - middle[3];
1045         if ((dxs == 0f && dys == 0f) || (dxf == 0f && dyf == 0f)) {
1046             dxs = dxf = middle[4] - middle[0];
1047             dys = dyf = middle[5] - middle[1];
1048         }
1049         if (dxs == 0f && dys == 0f) {
1050             // this happens iff the "curve" is just a point
1051             lineTo(middle[0], middle[1]);
1052             return;
1053         }
1054         // if these vectors are too small, normalize them, to avoid future
1055         // precision problems.
1056         if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
1057             float len = (float) sqrt(dxs*dxs + dys*dys);
1058             dxs /= len;
1059             dys /= len;
1060         }
1061         if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
1062             float len = (float) sqrt(dxf*dxf + dyf*dyf);
1063             dxf /= len;
1064             dyf /= len;
1065         }
1066 
1067         computeOffset(dxs, dys, lineWidth2, offset[0]);
1068         final float mx = offset[0][0];
1069         final float my = offset[0][1];
1070         drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, mx, my);
1071 
1072         int nSplits = findSubdivPoints(middle, subdivTs, 6, lineWidth2);
1073 
1074         int kind = 0;
1075         Iterator<Integer> it = Curve.breakPtsAtTs(middle, 6, subdivTs, nSplits);
1076         while(it.hasNext()) {
1077             int curCurveOff = it.next();
1078 
1079             kind = computeOffsetQuad(middle, curCurveOff, lp, rp);
1080             emitLineTo(lp[0], lp[1]);
1081             switch(kind) {
1082             case 6:
1083                 emitQuadTo(lp[0], lp[1], lp[2], lp[3], lp[4], lp[5], false);
1084                 emitQuadTo(rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], true);
1085                 break;
1086             case 4:
1087                 emitLineTo(lp[2], lp[3]);
1088                 emitLineTo(rp[0], rp[1], true);
1089                 break;
1090             }
1091             emitLineTo(rp[kind - 2], rp[kind - 1], true);
1092         }
1093 
1094         this.cmx = (lp[kind - 2] - rp[kind - 2]) / 2;
1095         this.cmy = (lp[kind - 1] - rp[kind - 1]) / 2;
1096         this.cdx = dxf;
1097         this.cdy = dyf;
1098         this.cx0 = xf;
1099         this.cy0 = yf;
1100         this.prev = DRAWING_OP_TO;
1101     }
1102 
1103     @Override public long getNativeConsumer() {
1104         throw new InternalError("Stroker doesn't use a native consumer");
1105     }
1106 
1107     // a stack of polynomial curves where each curve shares endpoints with
1108     // adjacent ones.
1109     private static final class PolyStack {
1110         float[] curves;
1111         int end;
1112         int[] curveTypes;
1113         int numCurves;
1114 
1115         private static final int INIT_SIZE = 50;
1116 
1117         PolyStack() {
1118             curves = new float[8 * INIT_SIZE];
1119             curveTypes = new int[INIT_SIZE];
1120             end = 0;
1121             numCurves = 0;
1122         }
1123 
1124         public boolean isEmpty() {
1125             return numCurves == 0;
1126         }
1127 
1128         private void ensureSpace(int n) {
1129             if (end + n >= curves.length) {
1130                 int newSize = (end + n) * 2;
1131                 curves = Arrays.copyOf(curves, newSize);
1132             }
1133             if (numCurves >= curveTypes.length) {
1134                 int newSize = numCurves * 2;
1135                 curveTypes = Arrays.copyOf(curveTypes, newSize);
1136             }
1137         }
1138 
1139         public void pushCubic(float x0, float y0,
1140                               float x1, float y1,
1141                               float x2, float y2)
1142         {
1143             ensureSpace(6);
1144             curveTypes[numCurves++] = 8;
1145             // assert(x0 == lastX && y0 == lastY)
1146 
1147             // we reverse the coordinate order to make popping easier
1148             curves[end++] = x2;    curves[end++] = y2;
1149             curves[end++] = x1;    curves[end++] = y1;
1150             curves[end++] = x0;    curves[end++] = y0;
1151         }
1152 
1153         public void pushQuad(float x0, float y0,
1154                              float x1, float y1)
1155         {
1156             ensureSpace(4);
1157             curveTypes[numCurves++] = 6;
1158             // assert(x0 == lastX && y0 == lastY)
1159             curves[end++] = x1;    curves[end++] = y1;
1160             curves[end++] = x0;    curves[end++] = y0;
1161         }
1162 
1163         public void pushLine(float x, float y) {
1164             ensureSpace(2);
1165             curveTypes[numCurves++] = 4;
1166             // assert(x0 == lastX && y0 == lastY)
1167             curves[end++] = x;    curves[end++] = y;
1168         }
1169 
1170         @SuppressWarnings("unused")
1171         public int pop(float[] pts) {
1172             int ret = curveTypes[numCurves - 1];
1173             numCurves--;
1174             end -= (ret - 2);
1175             System.arraycopy(curves, end, pts, 0, ret - 2);
1176             return ret;
1177         }
1178 
1179         public void pop(PathConsumer2D io) {
1180             numCurves--;
1181             int type = curveTypes[numCurves];
1182             end -= (type - 2);
1183             switch(type) {
1184             case 8:
1185                 io.curveTo(curves[end+0], curves[end+1],
1186                            curves[end+2], curves[end+3],
1187                            curves[end+4], curves[end+5]);
1188                 break;
1189             case 6:
1190                 io.quadTo(curves[end+0], curves[end+1],
1191                            curves[end+2], curves[end+3]);
1192                  break;
1193             case 4:
1194                 io.lineTo(curves[end], curves[end+1]);
1195             }
1196         }
1197 
1198         @Override
1199         public String toString() {
1200             String ret = "";
1201             int nc = numCurves;
1202             int end = this.end;
1203             while (nc > 0) {
1204                 nc--;
1205                 int type = curveTypes[numCurves];
1206                 end -= (type - 2);
1207                 switch(type) {
1208                 case 8:
1209                     ret += "cubic: ";
1210                     break;
1211                 case 6:
1212                     ret += "quad: ";
1213                     break;
1214                 case 4:
1215                     ret += "line: ";
1216                     break;
1217                 }
1218                 ret += Arrays.toString(Arrays.copyOfRange(curves, end, end+type-2)) + "\n";
1219             }
1220             return ret;
1221         }
1222     }
1223 }